<?php

/**
 * SigmaCMS - Content Management System
 *
 * Copyright (C) 2008 Pavol Biely
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 *
 * @package    SigmaCMS
 * @author     Pavol Biely <pavolbiely@gmail.com>
 * @copyright  2008 Pavol Biely
 * @license    http://www.gnu.org/licenses/gpl.txt   GNU General Public License
 * @link       http://pabi3.com/
 * @since      0.1.0
 */

abstract class Member
{
	/**
	 * Identification number of logged in member
	 *
	 * @var integer
	 */
	static private $_memberid = 0;

	/**
	 * Identification string of logged in member
	 *
	 * @var string
	 */
	static private $_username;

	/**
	 * Identification number of group in which member is logged in user
	 *
	 * @var integer
	 */
	static private $_groupid;

	/**
	 * Unique token
	 *
	 * @var string
	 */
	static private $_token;

	/**
	 * Expiration time of session
	 *
	 * @var integer
	 */
	static private $_expiration;

	/**
	 * Send cookie
	 *
	 * @return boolean
	 */
	static private function sendCookie($uniqueid, $destroy = false)
	{
		// set expiration time
		$expirationTime = abs(intval(cfg::get('cookie_expiration'))); 
		if ($expirationTime == 0) {
			$expire = 0;
		} else {
			self::$_expiration = time()+$expirationTime;
		}

		// should the cookie be removed?
		$expire = $destroy ? -self::$_expiration : self::$_expiration;

		// cookie name
		$cookieName = cfg::get('cookie_nameprefix').'members';

		// success?
		return setcookie(
		$cookieName
		,$uniqueid
		,$expire
		,cfg::get('cookie_path')
		,cfg::get('cookie_domain')
		,cfg::get('cookie_secure')
		,cfg::get('cookie_httponly')
		);
	}
	
	/**
	 * Log in a member
	 *
	 * @param string $username
	 * @param string $password
	 * @return boolean
	 */
	static public function logIn($username, $password)
	{
		// result
		$logIn = false;

		// member is blocked
		$blocked = false;

		// remove dangerous characters
		$username = _hsc($username);

		// load member information
		$sql = db::query("SELECT `id`,`username`,`group_id`,`blocked` FROM ".db::prefix('members')." WHERE `username`='".db::escape($username)."' AND `password`='".hash('sha512',$password.$username)."'");
		$row = db::fetchObject($sql);

		// does the member exist?
		if (db::numRows($sql) == 1) {
			// create a new session and check if the member is not blocked
			if ($row->blocked == 0) {
				// save information about member
				self::$_memberid = $row->id;
				self::$_username = $row->username;
				self::$_groupid  = $row->group_id;
				// create a new session
				$logIn = self::createSession();
			} else {
				// member is blocked
				$blocked = true;
				// log attempt to log in by blocked user
				Accesslog::add(sprintf(_LOG_MEMBER_LOGIN_BLOCKED,$row->username));
			}
		}

		// is member successfully logged in?
		if ($logIn && !$blocked) {
			// log access
			Accesslog::add(sprintf(_LOG_MEMBER_LOGIN,$username));

			// event of successfull log in
			Main::addEvent('logIn',$username);
		} else if (!$blocked) {
			// log access
			Accesslog::add(sprintf(_LOG_MEMBER_LOGIN_FAILED,$username,$password));

			// event of unsuccessfull log in
			Main::addEvent('logInFailed',$username);
		}

		return $logIn;
	}

	/**
	 * Log out
	 *
	 * @return void
	 */
	static public function logOut()
	{
		// unique member string
		$uniqueid = self::getUniqueID();

		// prepare tables for the JOIN
		$members  = db::prefix('members');
		$sessions = db::prefix('members_sessions');

		// get the member ID
		$sql = db::query("SELECT $members.`id` AS `id`,$members.`username` AS `username` FROM $members ".
		" LEFT JOIN $sessions".
		" ON $members.`id`=$sessions.`memberid`".
		" WHERE $sessions.`uniqueid`='".db::escape($uniqueid)."'");

		// load an information of the member, who will be logged out
		if (db::numRows($sql) > 0) {
			$row = db::fetchObject($sql);
			self::$_memberid = $row->id;
			self::$_username = $row->username;
	
			// log access
			Accesslog::add(sprintf(_LOG_MEMBER_LOGOUT,self::$_username));
	
			// delete session from database
			db::query("DELETE FROM ".db::prefix('members_sessions')." WHERE `memberid`='".db::escape(self::$_memberid)."'");
		}

		// destroy the cookie
		self::sendCookie(NULL,true);

		// delete an information about the member
		self::$_memberid = NULL;
		self::$_username = NULL;
		self::$_groupid  = NULL;
	}

	/**
	 * Cache of the Member::isloggedIn() method
	 *
	 * @var boolean
	 */
	static private $_loggedIn;

	/**
	 * Check if a member is logged in
	 *
	 * @return boolean
	 */
	static public function isLoggedIn()
	{
		// cookie name
		$cookieName = cfg::get('cookie_nameprefix').'members';

		// does the cookie exist?
		if (!isset($_COOKIE[$cookieName])) {
			self::logOut();
			return false;
		}

		// is the result of this method saved in the cache?
		if (self::$_loggedIn) {
			return self::$_loggedIn;
		}

		// prepare tables for the JOIN
		$members  = db::prefix('members');
		$sessions = db::prefix('members_sessions');

		// load the unique string from clients cookie
		$uniqueid = self::getUniqueID();

		// does the session exist in the database?
		$sql = db::query(
		"SELECT $members.`id` AS `memberid`,$members.`username` AS `username`,$members.`group_id` AS `group_id`,$sessions.`token` AS `token`".
		" FROM $members LEFT JOIN $sessions".
		" ON $members.`id`=$sessions.`memberid`".
		" WHERE `uniqueid`='".db::escape($uniqueid)."'"
		);

		// result
		$result = db::numRows($sql);

		// load username and group
		$row = db::fetchObject($sql);
		if ($result) {
			self::$_memberid = $row->memberid;
			self::$_username = $row->username;
			self::$_groupid  = $row->group_id;
			self::$_token    = $row->token;
		}

		// free resources
		db::free($sql);

		// save the result into the cache
		self::$_loggedIn = $result;

		return $result;
	}

	/**
	 * Extend expiration time for logged in member
	 *
	 * @return void
	 */
	static public function updateSession()
	{
		// member must be logged in
		if (!self::isLoggedIn()) {
			return;
		}

		// send new cookie, with longer expiration time
		self::sendCookie(self::getUniqueID());
	}

	/**
	 * Validate an username
	 *
	 * @param string $username
	 * @return boolean
	 */
	static public function isValidUsername($username)
	{
		if (preg_match('/^([a-zA-Z0-9_-]{4,20})$/',$username)) {
			return true;
		}
		return false;
	}

	/**
	 * Validate an e-mail
	 *
	 * @param string $email
	 * @return boolean
	 */
	static public function isValidEmail($email)
	{
		if (preg_match('/^[a-z0-9_\-\.%]+@[a-z0-9_\-\.%]+\.[a-z]{2,6}$/i',$email)) {
			return true;
		}
		return false;
	}

	/**
	 * Array holding all members
	 *
	 * @var array
	 */
	static private $_members = array();

	/**
	 * Load all members into an array
	 * 
	 * @return void
	 */
	static private function loadMembers()
	{
		// load all members into an array
		if (count(self::$_members) == 0) {
			$sql = db::query("SELECT `id`,`username` FROM %t", "members");
			while ($row = db::fetchObject($sql)) {
				self::$_members[$row->id] = $row->username;
			}
			db::free($sql);
		}
	}

	/**
	 * Get all members
	 *
	 * @param boolean $flip
	 * @return array
	 */
	static public function getAll($flip = false)
	{
		// load all members into an array
		self::loadMembers();

		// return a list of all members
		return $flip ? array_flip(self::$_members) : self::$_members;
	}

	/**
	 * Get an ID by the username
	 *
	 * @param string $username
	 * @return integer
	 */
	static public function getIdByUsername($username)
	{
		// load all members into an array
		self::loadMembers();

		// exchanges all IDs with their associated usernames
		$fMembers = array_flip(self::$_members);

		// return ID of the member
		return isset($fMembers[$username]) ? $fMembers[$username] : 0;
	}

	/**
	 * Get an username by the ID
	 *
	 * @param integer $id
	 * @return string
	 */
	static public function getUsernameById($id)
	{
		// load all members into an array
		self::loadMembers();

		// return username of the member
		return isset(self::$_members[$id]) ? self::$_members[$id] : NULL;
	}

	/**
	 * Get the ID
	 *
	 * @return string
	 */
	static public function getID()
	{
		return self::$_memberid;
	}

	/**
	 * Get the username of logged in member
	 *
	 * @return string
	 */
	static public function getUsername()
	{
		return self::$_username;
	}

	/**
	 * Get the group ID of logged in member
	 *
	 * @return integer
	 */
	static public function getGroup()
	{
		return (integer) self::$_groupid;
	}

	/**
	 * Get the unique token of logged in member
	 *
	 * @return string
	 */
	static public function getToken()
	{
		return self::$_token;
	}

	/**
	 * Get the actual unique ID of logged in member
	 *
	 * @return string
	 */
	static private function getUniqueID()
	{
		return _cookie(cfg::get('cookie_nameprefix').'members');
	}

	/**
	 * Randomly generate a new unique string
	 *
	 * @return string
	 */
	static private function newUniqueID()
	{
		return sha1(uniqid(mt_rand(),true));
	}

	/**
	 * Create a new session
	 *
	 * @return void
	 */
	static public function createSession()
	{
		// generate new unique ID
		$uniqueid = self::newUniqueID();

		// generate new unique token
		self::$_token = self::newUniqueID();

		// send cookie
		if (!self::sendCookie($uniqueid)) {
			return false;
		}

		// delete old sessions
		$sql = db::query("DELETE FROM ".db::prefix('members_sessions')." WHERE `memberid`='".db::escape(self::$_memberid)."'");
		if (!$sql) {
			return false;
		}

		// create new session
		$sql = db::query("INSERT INTO ".db::prefix('members_sessions').
		" (`memberid`,`uniqueid`,`token`) VALUES(".
		" '".db::escape(self::$_memberid)."',".
		" '".db::escape($uniqueid)."',".
		" '".db::escape(self::$_token)."'".
		" )");
		if (!$sql) {
			return false;
		}

		return true;
	}

	/**
	 * Does the member have access to section in the administration or a plugin?
	 *
	 * @param string $plugin
	 * @return boolean
	 */
	static public function hasAccess($plugin)
	{
		// member must be logged in
		if (!self::isLoggedIn())  {
			return false;
		}

		// cache the result of this method
		static $cache = array();

		// group of administrators have a full access
		if (self::getGroup() === 1) {
			return true;
		}

		// convert plugin name to lowercase
		$pluginAccess = 'plugin.'.strtolower($plugin);

		// is the plugin name valid?
		if (ctype_alnum($plugin)) {
			// return cached result if it is available
			if (isset($cache[$pluginAccess])) {
				return true;
			}

			// load access rights to administration sections and plugins
			$sql = db::query("SELECT `section` FROM ".db::prefix('members_sections')." WHERE `group_id`='".intval(self::getGroup())."'");
			while ($row = db::fetchObject($sql)) {
				// save to cache
				$cache[$row->section] = true;
			}
			db::free($sql);

			// return a fresh result if it is available
			if (isset($cache[$pluginAccess])) {
				return true;
			}
		}

		// member is not allowed to access this section/plugin
		return false;
	}

	/**
	 * Does the member exist?
	 *
	 * @param string $member
	 * @return boolean
	 */
	static public function exists($member)
	{
		if (false !== db::fQuery("SELECT `username` FROM ".db::prefix('members')." WHERE `username`='".db::escape($member)."'")) {
			return true;
		}
		return false;
	}
}
